package inkwash

import (
	"github.com/gdamore/tcell/v2"
)

const (
	// 设置通道缓冲区.
	queueSize = 100

)

//queuedUpdate表示排队执行f
//应用程序。队列更新（）。如果“done”不为空，则只收到一个
//f执行后的元素。
type queuedUpdate struct {
	f    func()
	done chan struct{}
}

type Application struct {
	events chan tcell.Event

	//从goroutines排队的函数，用于序列化原语的更新。
	updates chan queuedUpdate

	//调用Finish（）后，屏幕变量将被设置为的对象。
	//使用此通道为应用程序设置新的屏幕对象
	//（screen.Init（）和draw（）将被隐式调用）。值为nil将
	//停止应用程序。
	screenReplacement chan tcell.Screen
}

func NewApplication() *Application {
	return &Application{
		events:            make(chan tcell.Event, queueSize),
		updates:           make(chan queuedUpdate, queueSize),
		screenReplacement: make(chan tcell.Screen, 1),
	}
}

//Run启动应用程序，从而启动事件循环。此函数返回
//当Stop（）被调用时。
func (a *Application) Run() error {
	var (
		err, appErr error
		lastRedraw  time.Time   // The time the screen was last redrawn.
		redrawTimer *time.Timer // A timer to schedule the next redraw.
	)
	a.Lock()

	// Make a screen if there is none yet.
	if a.screen == nil {
		a.screen, err = tcell.NewScreen()
		if err != nil {
			a.Unlock()
			return err
		}
		if err = a.screen.Init(); err != nil {
			a.Unlock()
			return err
		}
		if a.enableMouse {
			a.screen.EnableMouse()
		} else {
			a.screen.DisableMouse()
		}
		if a.enablePaste {
			a.screen.EnablePaste()
		} else {
			a.screen.DisablePaste()
		}
	}

	// We catch panics to clean up because they mess up the terminal.
	defer func() {
		if p := recover(); p != nil {
			if a.screen != nil {
				a.screen.Fini()
			}
			panic(p)
		}
	}()

	// Draw the screen for the first time.
	a.Unlock()
	a.draw()

	// Separate loop to wait for screen events.
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			a.RLock()
			screen := a.screen
			a.RUnlock()
			if screen == nil {
				// We have no screen. Let's stop.
				a.QueueEvent(nil)
				break
			}

			// Wait for next event and queue it.
			event := screen.PollEvent()
			if event != nil {
				// Regular event. Queue.
				a.QueueEvent(event)
				continue
			}

			// A screen was finalized (event is nil). Wait for a new screen.
			screen = <-a.screenReplacement
			if screen == nil {
				// No new screen. We're done.
				a.QueueEvent(nil) // Stop the event loop.
				return
			}

			// We have a new screen. Keep going.
			a.Lock()
			a.screen = screen
			enableMouse := a.enableMouse
			enablePaste := a.enablePaste
			a.Unlock()

			// Initialize and draw this screen.
			if err := screen.Init(); err != nil {
				panic(err)
			}
			if enableMouse {
				screen.EnableMouse()
			} else {
				screen.DisableMouse()
			}
			if enablePaste {
				screen.EnablePaste()
			} else {
				screen.DisablePaste()
			}
			a.draw()
		}
	}()

	// Start event loop.
	var (
		pasteBuffer strings.Builder
		pasting     bool // Set to true while we receive paste key events.
	)
EventLoop:
	for {
		select {
		// If we received an event, handle it.
		case event := <-a.events:
			if event == nil {
				break EventLoop
			}

			switch event := event.(type) {
			case *tcell.EventKey:
				// If we are pasting, collect runes, nothing else.
				if pasting {
					switch event.Key() {
					case tcell.KeyRune:
						pasteBuffer.WriteRune(event.Rune())
					case tcell.KeyEnter:
						pasteBuffer.WriteRune('\n')
					case tcell.KeyTab:
						pasteBuffer.WriteRune('\t')
					}
					break
				}

				a.RLock()
				root := a.root
				inputCapture := a.inputCapture
				a.RUnlock()

				// Intercept keys.
				var draw bool
				originalEvent := event
				if inputCapture != nil {
					event = inputCapture(event)
					if event == nil {
						a.draw()
						break // Don't forward event.
					}
					draw = true
				}

				// Ctrl-C closes the application.
				if event == originalEvent && event.Key() == tcell.KeyCtrlC {
					a.Stop()
					break
				}

				// Pass other key events to the root primitive.
				if root != nil && root.HasFocus() {
					if handler := root.InputHandler(); handler != nil {
						handler(event, func(p Primitive) {
							a.SetFocus(p)
						})
						draw = true
					}
				}

				// Redraw.
				if draw {
					a.draw()
				}
			case *tcell.EventPaste:
				if !a.enablePaste {
					break
				}
				if event.Start() {
					pasting = true
					pasteBuffer.Reset()
				} else if event.End() {
					pasting = false
					a.RLock()
					root := a.root
					a.RUnlock()
					if root != nil && root.HasFocus() && pasteBuffer.Len() > 0 {
						// Pass paste event to the root primitive.
						if handler := root.PasteHandler(); handler != nil {
							handler(pasteBuffer.String(), func(p Primitive) {
								a.SetFocus(p)
							})
						}

						// Redraw.
						a.draw()
					}
				}
			case *tcell.EventResize:
				if time.Since(lastRedraw) < redrawPause {
					if redrawTimer != nil {
						redrawTimer.Stop()
					}
					redrawTimer = time.AfterFunc(redrawPause, func() {
						a.events <- event
					})
				}
				a.RLock()
				screen := a.screen
				a.RUnlock()
				if screen == nil {
					break
				}
				lastRedraw = time.Now()
				screen.Clear()
				a.draw()
			case *tcell.EventMouse:
				consumed, isMouseDownAction := a.fireMouseActions(event)
				if consumed {
					a.draw()
				}
				a.lastMouseButtons = event.Buttons()
				if isMouseDownAction {
					a.mouseDownX, a.mouseDownY = event.Position()
				}
			case *tcell.EventError:
				appErr = event
				a.Stop()
			}

		// If we have updates, now is the time to execute them.
		case update := <-a.updates:
			update.f()
			if update.done != nil {
				update.done <- struct{}{}
			}
		}
	}

	// Wait for the event loop to finish.
	wg.Wait()
	a.screen = nil

	return appErr
}